" vim:ft=vim
"
" Test the lambda() function
"

Before;
let args = [
      \ ['x', 'toupper(x)'],
      \ ['x y', 'x + y'],
      \ ['d', 'has_key(d, "foo")'],
      \ ['x y z', '[x, y, z]'],
      \ ['x y', 'x . y'],
      \]

let eval_answers = [
      \ [ ['a'], 'A'],
      \ [ [1, 2], 3],
      \ [ [{'foo': 1}], 1],
      \ [ [ 1, 2, 3], [1, 2, 3]],
      \ [ ['This is ', 'an apple'], 'This is an apple'],
      \]

let N = len(args)
AssertEqual N, len(eval_answers)


"
" Create lambda Funcref and object and ensure that they produce
" desired results when called.
"
Execute (The Funcref returned by lambda() can compute correct results);
for i in range(N)
  " Note: L is already a Funcref so just call() it.
  let L = call('object#lambda', args[i])
  let lambda_out = call(L, eval_answers[i][0])
  AssertEqual lambda_out, eval_answers[i][1]
endfor


Execute (The object returned by _lambda() has correct attributes);
for x in range(N)
  let f = call('object#_lambda', args[x])
  AssertEqual f.__code__, args[x][1]
  AssertEqual f.__argv__, object#lambda#make_names(args[x][0])
  AssertEqual call(f.__call__, eval_answers[x][0]), eval_answers[x][1]
endfor


Execute (Examine the repr of lambda object);
let f = object#_lambda('x', 'x + 1', g:)
Log object#repr(object#type(f))
Log object#repr(f)
Log object#dir(f)


Execute (Corner case: taking no argument);
let L = object#_lambda('', '1')
AssertEqual L.__argv__, []
AssertEqual L.__call__(), 1

"
" Make sure closure works
"

Execute (Closure works for l:);
function! TestLocal()
  let l:foo = 1
  let f = object#_lambda('', 'c.foo', l:)
  Assert has_key(f, '__closure__')
  AssertEqual f.__closure__, l:
  AssertEqual f.__call__(), l:foo
endfunction

call TestLocal()


Execute (Closure works for a:);
function! TestArgs(list)
  let f = object#_lambda('', 'add(c.list, 1)', a:)
  AssertEqual a:, f.__closure__
  call f.__call__()
endfunction

let list = []
call TestArgs(list)
AssertEqual list, [1]


Execute (Closure works for s:);
let s:script_var = 1
let f = object#_lambda('', 'c.script_var', s:)
" The s: in AssertEqual will be the one of the vader code.
" AssertEqual f.__closure__, s:

for x in range(10)
  let s:script_var = x
  AssertEqual f.__call__(), x
endfor


Execute (Closure works for g:);
let g:this_is_an_intended_long_name = 10
let f = object#_lambda('', '1 + c.this_is_an_intended_long_name', g:)
AssertEqual f.__call__(), g:this_is_an_intended_long_name + 1

"
" Various throwing conditions
"
Execute (Throws TypeError when formal > actual);
AssertThrows call object#lambda('x y z', 'x > 0 ? y : z')(1, 2)
Log g:vader_exception
Assert g:vader_exception =~# 'TypeError'


Execute (Throws TypeError when formal < actual);
AssertThrows call object#lambda('x y', 'x > 0 ? y : z')(1, 2, 3)
Log g:vader_exception
Assert g:vader_exception =~# 'TypeError'


Execute (Throws WrongType when closure given but not a Dict);
AssertThrows call object#lambda('x', 'string(x)', 1)
Log g:vader_exception
Assert g:vader_exception =~# 'WrongType'


Execute (Throws Undefined variable when closure is used in code but not given);
AssertThrows call object#lambda('', 'c.undefined')()
Log g:vader_exception
Assert g:vader_exception =~# 'Undefined'

AssertThrows call object#lambda('', 'undefined')()
Assert g:vader_exception =~# 'Undefined'
Log g:vader_exception


Execute (Impl variable ``a:__lambda`` can be accessed from lambda);
" Create a lambda that simply returns the lambda itself
let f = object#_lambda('', 'a:__lambda')
AssertEqual f.__call__(), f


"
" The WrongType throwing when code or name is not valid String
" is not tested because they are based on maktaba#ensure library
" which should do its job well.
"
Execute (The limit of number of arguments is 20);
let limit = 20
let args = join(map(range(limit), '"arg_" . string(v:val)'))
let F = object#lambda(args, '1')
AssertEqual 1, call(F, range(limit))


Execute (Throws ValueError if duplicate names are given);
AssertThrows call object#lambda('x x', 'x')
Assert g:vader_exception =~# 'ValueError'
Log g:vader_exception
